/**
 * Created by JustinChow on 2016/11/3.
 */

"use strict";

const Vector    = require("../Utils/Vector");
const Rectangle = require("../Utils/Rectangle");
const Logger    = require("pomelo-logger").getLogger("PlayerTracker", __filename);

const APIStaticConfig = require("../Configs/APIStaticConfig.json");

function PlayerTracker(gameServer, socket, propsHandler) {

    this.gameServer = gameServer;
    this.socket = socket;
    this.disconnect = -1; // Disconnection
    this.fullyDisconnected = false;

    this.playerID = -1;
    this.name = "";
    this.score = 0;

    /**
     * @type Player
     */
    this.player = null;
    this.rotation = 0;
    this.radian = 0;
    this.speed = new Vector();
    this.collide = false;

    this.propsHandler = propsHandler;

    this.tickView              = 0;
    this.visibleNodes          = [];    // Current Visible Objects
    this.nodeAdditionQueue     = [];    // Newly Added Objects
    this.nodeDestroyQueue      = [];    // Newly Destroyed Objects

    this.tickRankList          = 0;     // Time Left To Update RankList

    this.isObserver = false;

    if (gameServer) {
        this.playerID = gameServer.getNewClientId();
    }
}

module.exports = PlayerTracker;

PlayerTracker.prototype.setName = function (name) {
    this.name = name;
};
PlayerTracker.prototype.getName = function () {
    return this.name;
};
PlayerTracker.prototype.getScore = function () {
    // Todo If Need To Recalculate Score
    return this.score;
};

/**
 * User Move
 *
 * @param {Number} rotation
 * @param {Number} radian
 * @param {Number} x
 * @param {Number} y
 */
PlayerTracker.prototype.setTarget = function (rotation, radian, x, y) {
    // TODO Every API, We Must Verify The Data Sent From Client
    // if (parseFloat(rotation) != rotation || parseFloat(radian) != radian
    // || parseFloat(x) != x || parseFloat(y) != y) {
    //    Logger.warn("Receive Client Data [ %j ]", arguments);
    //    return;
    //}
    this.rotation = rotation;
    this.radian = radian;
    this.speed = new Vector(x, y);
    // this.player.move();
    // this.socket.sendPacket(APIStaticConfig.ON_SET_TARGET, {"rotation": rotation, "radian": radian, "x": x, "y": y});
};

PlayerTracker.prototype.useProps = function (propsId, position) {
    // No Position May Means To Cast Spell To Myself
    if (!position) position = this.player.position;

    this.propsHandler.onPropsSpawn(propsId, position);
};

/**
 * This Function Is Called From UserHandler.update From GameServer.mainLoop Every tickTick Time
 */
PlayerTracker.prototype.update = function () {
    if (this.socket.packetHandler.protocolVersion == 0) return;

    var time = new Date();

    // Todo Handle Client Props Function
    this.propsHandler.update();

    var updateNodes = []; // Nodes that need to be updated via packet
    var nonVisibleNodes = []; // Nodes that are not visible anymore

    for (let i = 0; i < this.nodeAdditionQueue.length; i++) {
        var currentBox = this.getBox();
        var range = this.nodeAdditionQueue[i].getRange();
        var inspect = currentBox.intersects(range);

        if (!inspect) continue;

        // if (!(this.getBox().intersects(this.nodeAdditionQueue[i].getRange()))) continue;
        this.visibleNodes.push(this.nodeAdditionQueue[i]);
        updateNodes.push(this.nodeAdditionQueue[i]);
    }
    for (let j = 0; j < this.nodeDestroyQueue.length; j++) {
        if (this.visibleNodes.indexOf(this.nodeDestroyQueue[j]) == -1) continue; // Wasn't visible anyway
        // this.visibleNodes.remove(this.nodeDestroyQueue[j]);
        for( let tmpId = 0, tmpLen = this.visibleNodes.length; tmpId < tmpLen ; tmpId ++){
            let containId = this.visibleNodes.indexOf(this.nodeDestroyQueue[j]);
            if (containId > -1) this.visibleNodes.splice(containId, 1);
        }
        nonVisibleNodes.push(this.nodeDestroyQueue[j]);
    }

    if (this.tickView > 5) {
        var newBox = this.getBox();
        var newNodes = this.calcVisibleNodes(newBox);
        var currentNodes = this.visibleNodes;

        // Compare newly visible nodes to currently visible
        for (let k = 0; k < newNodes.length; k++) {
            var nid = currentNodes.indexOf(newNodes[k]);
            // New visible node
            if (nid == -1) updateNodes.push(newNodes[k]);
        }

        // Compare currently visible nodes to newly visible
        for (let m = 0; m < currentNodes.length; m++) {
            var oid = newNodes.indexOf(currentNodes[m]);
            // Not visible anymore
            if (oid == -1) nonVisibleNodes.push(currentNodes[m]);
        }

        this.visibleNodes = newNodes;

        // Debug The Reason Disconnected ws.playerTracker.player Still In QuadTree
        // if (!this.player.isBot) {
        //     var playerIds = [];
        //     this.visibleNodes.forEach(node => {
        //         playerIds.push(node.objectId);
        //     });
        //     Logger.warn("Player Visible Nodes Are: User [ %s ] Enemy [ %j ]", this.playerID, playerIds);
        // }

        this.tickView = 0;
    } else {
        this.tickView += 1;
    }

    for (let n = 0; n < this.visibleNodes.length; n++) {
        if (!this.visibleNodes[n]) continue;
        // Don't check for update if it's going to be updated
        if (updateNodes.indexOf(this.visibleNodes[n]) == -1 && this.visibleNodes[n].sendUpdate()) {
            updateNodes.push(this.visibleNodes[n]);
        }
    }

    var dead = [], enemy = [], nonVisible = [];
    for (let p = 0; p < nonVisibleNodes.length; p++) {
        if (!nonVisibleNodes[p]) continue;
        if (nonVisibleNodes[p].getKiller()){
            deads.push({
                "killerId": nonVisibleNodes[p].getKiller().objectId,
                "deadId": nonVisibleNodes[p].objectId
            });
        }
        nonVisible.push(nonVisibleNodes[p].objectId);
    }
    for (let q = 0;q < updateNodes.length; q++){
        if (!updateNodes[q]
            || !updateNodes[q].objectId
            || updateNodes[q].objectId == this.player.objectId
        ) continue;

        // Todo Monitor That ".toFixed" Function Must Be Called Via A Number, Not Null
        enemy.push({
            "objectId": updateNodes[q].objectId,
            "position": {
                "x": parseFloat(updateNodes[q].position.x.toFixed(4)),
                "y": parseFloat(updateNodes[q].position.y.toFixed(4))
            },
            "rotation": updateNodes[q].owner.rotation,
            "radian": parseFloat(updateNodes[q].owner.radian.toFixed(4)),
            "proportion": {
                "x": parseFloat(updateNodes[q].owner.speed.x.toFixed(4)),
                "y": parseFloat(updateNodes[q].owner.speed.y.toFixed(4))
            }
        });
    }

    // CHANGE TO THE FOLLOWING STRUCTURE 2016-11-14 19:30:28 By JustinChou, To Fit The Data Structure Defined In Client
    this.socket.sendPacket(APIStaticConfig.TO_UPDATE_MAP, {
        "dead": dead,
        "player": {
            "objectId": this.player.objectId,
            "rotation": parseFloat(this.rotation.toFixed(4)),
            "position": {
                "x": parseFloat(this.player.position.x.toFixed(4)),
                "y": parseFloat(this.player.position.y.toFixed(4))
            },
            "radian": parseFloat(this.radian.toFixed(4))
        },
        "enemy": enemy,
        "nonVisible": nonVisible
    });

    this.nodeDestroyQueue = []; // Reset destroy queue
    this.nodeAdditionQueue = []; // Reset addition queue

    // Todo Update LeaderBoard



    // This Code Is Add For Debug Purpose
    // Because At First The Return allPlayer Sometimes Contains *NOT* The Player Himself
    // Reason: The Init QuadTree Range Is Different From (Smaller Than) That Of The Map (The Position That Users Can Reach)
    // So: The User List From QuadTree Sometimes Comtains Not The Right Users, Because It Regards The User Is Out Of Range
    // Fix Method: Pass The Right Range On Initialization In GameServer.start().
    // Date: 2016-11-10 17:50 By: Justin Chow
    // 
    // var noMyData = true;
    // for (var pid = 0; pid < allPlayer.length; pid++) {
    //     if (allPlayer[pid].objectId == this.player.objectId) noMyData = false;
    // }
    // if (noMyData) {
    //     Logger.error("Send Data Contains NOT Myself Data!!!!!");
    // }



    // User Is Disconnect Or Fully Disconnect
    if (this.disconnect > -1) {
        this.disconnect -= 1;

        // Debug The Reason Disconnected ws.playerTracker.player Still In QuadTree
        // console.log("Start To Disconnect [ %s ] ", this.disconnect, this.player ? this.player.objectId : null);
        if (this.disconnect == -1 || this.player == null) {
            // Todo Remove User Data From List When Disconnected.
            this.gameServer.removeNode(this.player);
            this.fullyDisconnected = true;

            // Debug The Reason Disconnected ws.playerTracker.player Still In QuadTree
            // console.log("FullyDisconnected [ %s ]", this.fullyDisconnected);
        }
    }

    this.gameServer.tickPlayerTracker = new Date() - time;
};

PlayerTracker.prototype.getBox = function() { // For view distance
    return new Rectangle(
        this.player.position.x - this.gameServer.config.viewWidth / 2,
        this.player.position.y - this.gameServer.config.viewHeight / 2,
        this.gameServer.config.viewWidth,
        this.gameServer.config.viewHeight
    );
};

PlayerTracker.prototype.sendPosPacket = function () {

};

PlayerTracker.prototype.calcVisibleNodes = function (box) {
    return this.gameServer.quadTree.query(box);
};

PlayerTracker.prototype.calcMapPosition = function () {

};